package com.our123.common.web.cos;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;

import javax.servlet.*;
import javax.servlet.http.*;

import com.our123.common.web.RequestUtils;

/** 
 * A utility class to handle <code>multipart/form-data</code> requests,
 * the kind of requests that support file uploads.  This class emulates the 
 * interface of <code>HttpServletRequest</code>, making it familiar to use. 
 * It uses a "push" model where any incoming files are read and saved directly
 * to disk in the constructor. If you wish to have more flexibility, e.g. 
 * write the files to a database, use the "pull" model 
 * <code>MultipartParser</code> instead.
 * <p>
 * This class can receive arbitrarily large files (up to an artificial limit 
 * you can set), and fairly efficiently too.  
 * It cannot handle nested data (multipart content within multipart content).
 * It <b>can</b> now with the latest release handle internationalized content
 * (such as non Latin-1 filenames).
 * <p>
 * To avoid collisions and have fine control over file placement, there's a
 * constructor variety that takes a pluggable FileRenamePolicy implementation.
 * A particular policy can choose to rename or change the location of the file
 * before it's written.
 * <p>
 * See the included upload.war for an example of how to use this class.
 * <p>
 * The full file upload specification is contained in experimental RFC 1867,
 * available at <a href="http://www.ietf.org/rfc/rfc1867.txt">
 * http://www.ietf.org/rfc/rfc1867.txt</a>.
 *
 * @see MultipartParser
 * 
 * @author Jason Hunter
 * @author Geoff Soutter
 * @version 1.12, 2004/04/11, added null check for Opera malformed bug<br>
 * @version 1.11, 2002/11/01, combine query string params in param list<br>
 * @version 1.10, 2002/05/27, added access to the original file names<br>
 * @version 1.9, 2002/04/30, added support for file renaming, thanks to
 *                           Changshin Lee<br>
 * @version 1.8, 2002/04/30, added support for internationalization, thanks to
 *                           Changshin Lee<br>
 * @version 1.7, 2001/02/07, made fields protected to increase user flexibility<br>
 * @version 1.6, 2000/07/21, redid internals to use MultipartParser,
 *                           thanks to Geoff Soutter<br>
 * @version 1.5, 2000/02/04, added auto MacBinary decoding for IE on Mac<br>
 * @version 1.4, 2000/01/05, added getParameterValues(),
 *                           WebSphere 2.x getContentType() workaround,
 *                           stopped writing empty "unknown" file<br>
 * @version 1.3, 1999/12/28, IE4 on Win98 lastIndexOf("boundary=")
 * workaround<br>
 * @version 1.2, 1999/12/20, IE4 on Mac readNextPart() workaround<br>
 * @version 1.1, 1999/01/15, JSDK readLine() bug workaround<br>
 * @version 1.0, 1998/09/18<br>
 */
public class CosMultipartRequest {

	private static final int DEFAULT_MAX_POST_SIZE = 1024 * 1024; // 1 Meg

	protected Hashtable<String, List<String>> parameters = new Hashtable<String, List<String>>(); // name - Vector of values
	protected Map<String, UploadedFile> files = new HashMap<String, UploadedFile>(); // name - UploadedFile

	/**
	 * Constructs a new MultipartRequest to handle the specified request, 
	 * saving any uploaded files to the given directory, and limiting the 
	 * upload size to 1 Megabyte.  If the content is too large, an
	 * IOException is thrown.  This constructor actually parses the 
	 * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	 * problem reading or parsing the request.
	 *
	 * @param request the servlet request.
	 * @param saveDirectory the directory in which to save any uploaded files.
	 * @exception IOException if the uploaded content is larger than 1 Megabyte
	 * or there's a problem reading or parsing the request.
	 */
	public CosMultipartRequest(HttpServletRequest request, String saveDirectory) throws IOException {
		this(request, saveDirectory, DEFAULT_MAX_POST_SIZE);
	}

	/**
	 * Constructs a new MultipartRequest to handle the specified request, 
	 * saving any uploaded files to the given directory, and limiting the 
	 * upload size to the specified length.  If the content is too large, an 
	 * IOException is thrown.  This constructor actually parses the 
	 * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	 * problem reading or parsing the request.
	 *
	 * @param request the servlet request.
	 * @param saveDirectory the directory in which to save any uploaded files.
	 * @param maxPostSize the maximum size of the POST content.
	 * @exception IOException if the uploaded content is larger than 
	 * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	 */
	public CosMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize) throws IOException {
		this(request, saveDirectory, maxPostSize, null, null);
	}

	/**
	 * Constructs a new MultipartRequest to handle the specified request, 
	 * saving any uploaded files to the given directory, and limiting the 
	 * upload size to the specified length.  If the content is too large, an 
	 * IOException is thrown.  This constructor actually parses the 
	 * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	 * problem reading or parsing the request.
	 *
	 * @param request the servlet request.
	 * @param saveDirectory the directory in which to save any uploaded files.
	 * @param encoding the encoding of the response, such as ISO-8859-1
	 * @exception IOException if the uploaded content is larger than 
	 * 1 Megabyte or there's a problem reading or parsing the request.
	 */
	public CosMultipartRequest(HttpServletRequest request, String saveDirectory, String encoding) throws IOException {
		this(request, saveDirectory, DEFAULT_MAX_POST_SIZE, encoding, null);
	}

	/**
	 * Constructs a new MultipartRequest to handle the specified request, 
	 * saving any uploaded files to the given directory, and limiting the 
	 * upload size to the specified length.  If the content is too large, an 
	 * IOException is thrown.  This constructor actually parses the 
	 * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	 * problem reading or parsing the request.
	 *
	 * @param request the servlet request.
	 * @param saveDirectory the directory in which to save any uploaded files.
	 * @param maxPostSize the maximum size of the POST content.
	 * @param policy the rules for renaming in case of file name collisions
	 * @exception IOException if the uploaded content is larger than 
	 * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	 */
	public CosMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize, FileRenamePolicy policy) throws IOException {
		this(request, saveDirectory, maxPostSize, null, policy);
	}

	/**
	 * Constructs a new MultipartRequest to handle the specified request, 
	 * saving any uploaded files to the given directory, and limiting the 
	 * upload size to the specified length.  If the content is too large, an 
	 * IOException is thrown.  This constructor actually parses the 
	 * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	 * problem reading or parsing the request.
	 *
	 * @param request the servlet request.
	 * @param saveDirectory the directory in which to save any uploaded files.
	 * @param maxPostSize the maximum size of the POST content.
	 * @param encoding the encoding of the response, such as ISO-8859-1
	 * @exception IOException if the uploaded content is larger than 
	 * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	 */
	public CosMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize, String encoding) throws IOException {
		this(request, saveDirectory, maxPostSize, encoding, null);
	}

	/**
	 * Constructs a new MultipartRequest to handle the specified request, 
	 * saving any uploaded files to the given directory, and limiting the 
	 * upload size to the specified length.  If the content is too large, an 
	 * IOException is thrown.  This constructor actually parses the 
	 * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	 * problem reading or parsing the request.
	 *
	 * To avoid file collisions, this constructor takes an implementation of the
	 * FileRenamePolicy interface to allow a pluggable rename policy.
	 *
	 * @param request the servlet request.
	 * @param saveDirectory the directory in which to save any uploaded files.
	 * @param maxPostSize the maximum size of the POST content.
	 * @param encoding the encoding of the response, such as ISO-8859-1
	 * @param policy a pluggable file rename policy
	 * @exception IOException if the uploaded content is larger than 
	 * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	 */
	public CosMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize, String encoding, FileRenamePolicy policy)
			throws IOException {
		// Sanity check values
		if (request == null)
			throw new IllegalArgumentException("request cannot be null");
		if (saveDirectory == null)
			throw new IllegalArgumentException("saveDirectory cannot be null");
		if (maxPostSize <= 0) {
			throw new IllegalArgumentException("maxPostSize must be positive");
		}

		// Save the dir
		File dir = new File(saveDirectory);

		// Check saveDirectory is truly a directory
		if (!dir.isDirectory())
			throw new IllegalArgumentException("Not a directory: " + saveDirectory);

		// Check saveDirectory is writable
		if (!dir.canWrite())
			throw new IllegalArgumentException("Not writable: " + saveDirectory);

		// Parse the incoming multipart, storing files in the dir provided, 
		// and populate the meta objects which describe what we found
		MultipartParser parser = new MultipartParser(request, maxPostSize, true, true, encoding);

		// Some people like to fetch query string parameters from
		// MultipartRequest, so here we make that possible.  Thanks to 
		// Ben Johnson, ben.johnson@merrillcorp.com, for the idea.
		if (request.getQueryString() != null) {
			// Let HttpUtils create a name->String[] structure
			Map<String, String[]> queryParameters = RequestUtils.parseQueryString(request.getQueryString());
			// For our own use, name it a name->Vector structure
			for (Entry<String, String[]> entry : queryParameters.entrySet()) {
				parameters.put(entry.getKey(), Arrays.asList(entry.getValue()));
			}
		}

		Part part;
		while ((part = parser.readNextPart()) != null) {
			String name = part.getName();
			if (name == null) {
				throw new IOException("Malformed input: parameter name missing (known Opera 7 bug)");
			}
			if (part.isParam()) {
				// It's a parameter part, add it to the vector of values
				ParamPart paramPart = (ParamPart) part;
				String value = paramPart.getStringValue();
				List<String> existingValues = parameters.get(name);
				if (existingValues == null) {
					existingValues = new ArrayList<String>();
					parameters.put(name, existingValues);
				}
				existingValues.add(value);
			} else if (part.isFile()) {
				// It's a file part
				FilePart filePart = (FilePart) part;
				String fileName = filePart.getFileName();
				if (fileName != null) {
					filePart.setRenamePolicy(policy); // null policy is OK
					// The part actually contained a file
					filePart.writeTo(dir);
					files.put(name, new UploadedFile(dir.toString(), filePart.getFileName(), fileName, filePart.getContentType()));
				} else {
					// The field did not contain a file
					files.put(name, new UploadedFile(null, null, null, null));
				}
			}
		}
	}

	/**
	 * Constructor with an old signature, kept for backward compatibility.
	 * Without this constructor, a servlet compiled against a previous version 
	 * of this class (pre 1.4) would have to be recompiled to link with this 
	 * version.  This constructor supports the linking via the old signature.
	 * Callers must simply be careful to pass in an HttpServletRequest.
	 * 
	 */
	public CosMultipartRequest(ServletRequest request, String saveDirectory) throws IOException {
		this((HttpServletRequest) request, saveDirectory);
	}

	/**
	 * Constructor with an old signature, kept for backward compatibility.
	 * Without this constructor, a servlet compiled against a previous version 
	 * of this class (pre 1.4) would have to be recompiled to link with this 
	 * version.  This constructor supports the linking via the old signature.
	 * Callers must simply be careful to pass in an HttpServletRequest.
	 * 
	 */
	public CosMultipartRequest(ServletRequest request, String saveDirectory, int maxPostSize) throws IOException {
		this((HttpServletRequest) request, saveDirectory, maxPostSize);
	}

	/**
	 * Returns the names of all the parameters as an Enumeration of 
	 * Strings.  It returns an empty Enumeration if there are no parameters.
	 *
	 * @return the names of all the parameters as an Enumeration of Strings.
	 */
	public Enumeration<String> getParameterNames() {
		return parameters.keys();
	}

	/**
	 * Returns the names of all the uploaded files as an Enumeration of 
	 * Strings.  It returns an empty Enumeration if there are no file input 
	 * fields on the form.  Any file input field that's left empty will result 
	 * in a FilePart with null contents.  Each file name is the name specified
	 * by the form, not by the user.
	 *
	 * @return the names of all the uploaded files as an Enumeration of Strings.
	 */
	public Set<String> getFileNames() {
		return files.keySet();
	}

	/**
	 * Returns the value of the named parameter as a String, or null if 
	 * the parameter was not sent or was sent without a value.  The value 
	 * is guaranteed to be in its normal, decoded form.  If the parameter 
	 * has multiple values, only the last one is returned (for backward 
	 * compatibility).  For parameters with multiple values, it's possible
	 * the last "value" may be null.
	 *
	 * @param name the parameter name.
	 * @return the parameter value.
	 */
	public String getParameter(String name) {
		try {
			List<String> values = parameters.get(name);
			if (values == null || values.size() == 0) {
				return null;
			}
			return values.get(values.size() - 1);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Returns the values of the named parameter as a String array, or null if 
	 * the parameter was not sent.  The array has one entry for each parameter 
	 * field sent.  If any field was sent without a value that entry is stored 
	 * in the array as a null.  The values are guaranteed to be in their 
	 * normal, decoded form.  A single value is returned as a one-element array.
	 *
	 * @param name the parameter name.
	 * @return the parameter values.
	 */
	public String[] getParameterValues(String name) {
		List<String> values = parameters.get(name);
		if (values == null || values.size() == 0) {
			return null;
		} else {
			return values.toArray(new String[values.size()]);
		}
	}

	/**
	 * Returns the filesystem name of the specified file, or null if the 
	 * file was not included in the upload.  A filesystem name is the name 
	 * specified by the user.  It is also the name under which the file is 
	 * actually saved.
	 *
	 * @param name the html page's file parameter name.
	 * @return the filesystem name of the file.
	 */
	public String getFilesystemName(String name) {
		try {
			UploadedFile file = files.get(name);
			return file.getFilesystemName(); // may be null
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Returns the original filesystem name of the specified file (before any
	 * renaming policy was applied), or null if the file was not included in 
	 * the upload.  A filesystem name is the name specified by the user.
	 *
	 * @param name the html page's file parameter name.
	 * @return the original file name of the file.
	 */
	public String getOriginalFileName(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getOriginalFileName(); // may be null
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Returns the content type of the specified file (as supplied by the 
	 * client browser), or null if the file was not included in the upload.
	 *
	 * @param name the html page's file parameter name.
	 * @return the content type of the file.
	 */
	public String getContentType(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getContentType(); // may be null
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Returns a File object for the specified file saved on the server's 
	 * filesystem, or null if the file was not included in the upload.
	 *
	 * @param name the html page's file parameter name.
	 * @return a File object for the named file.
	 */
	public File getFile(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getFile(); // may be null
		} catch (Exception e) {
			return null;
		}
	}
}

// A class to hold information about an uploaded file.
//
class UploadedFile {

	private String dir;
	private String filename;
	private String original;
	private String type;

	UploadedFile(String dir, String filename, String original, String type) {
		this.dir = dir;
		this.filename = filename;
		this.original = original;
		this.type = type;
	}

	public String getContentType() {
		return type;
	}

	public String getFilesystemName() {
		return filename;
	}

	public String getOriginalFileName() {
		return original;
	}

	public File getFile() {
		if (dir == null || filename == null) {
			return null;
		} else {
			return new File(dir + File.separator + filename);
		}
	}
}
